package com.star.common.web.advice;

import cn.hutool.core.text.CharSequenceUtil;
import com.star.common.core.context.ThreadContext;
import com.star.common.core.contract.R;
import com.star.common.core.contract.constant.ContextConstants;
import com.star.common.core.contract.enums.ResultCodeEnum;
import com.star.common.core.contract.exception.ServiceErrorException;
import com.star.common.core.contract.exception.ServiceException;
import com.star.common.core.contract.exception.ValidateException;
import com.star.common.core.utils.ExceptionKit;
import com.star.common.core.utils.JsonKit;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.ui.Model;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;

import java.sql.SQLException;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 异常处理
 *
 * @author zhouhengzhe
 */
@Slf4j
public class BaseGlobalExceptionAdvice {
    @ExceptionHandler({BindException.class})
    public R<String> bindException(HttpServletRequest request, Model model, BindException e) {
        String projectStackTrace = ExceptionKit.getProjectStackTraces(e);
        List<String> errList = e.getFieldErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).toList();
        ThreadContext.set(ContextConstants.SEE, projectStackTrace);
        log.error("请求参数校验失败：{} {},\n@See {}", errList, model, projectStackTrace);
        return R.fail(ResultCodeEnum.PARAMETER_VALIDATION_FAILED.getCode(), StringUtils.join(errList, ","));
    }

    @ExceptionHandler({ValidateException.class})
    public R<String> validatorException(HttpServletRequest request, ValidateException e) {
        String projectStackTrace = ExceptionKit.getProjectStackTraces(e);
        ThreadContext.set(ContextConstants.SEE, projectStackTrace);
        log.error("请求参数校验失败：{}，\n@See {}", e.getMsg(), projectStackTrace);
        return R.fail(ResultCodeEnum.PARAMETER_VALIDATION_FAILED.getCode(), e.getMsg());
    }

    @ExceptionHandler({MethodArgumentNotValidException.class})
    public R<String> methodArgumentNotValidExceptionHandler(HttpServletRequest request, MethodArgumentNotValidException e) {
        FieldError fieldError = e.getBindingResult().getFieldError();
        String projectStackTrace = ExceptionKit.getProjectStackTraces(e);
        ThreadContext.set(ContextConstants.SEE, projectStackTrace);
        assert fieldError != null;
        log.error("请求参数校验失败：{}，\n@See {}", fieldError.getDefaultMessage(), projectStackTrace);
        return R.fail(ResultCodeEnum.PARAMETER_VALIDATION_FAILED.getCode(), fieldError.getDefaultMessage());
    }

    @ExceptionHandler({ServiceException.class})
    public R<String> serviceException(HttpServletRequest request, ServiceException e) {
        String projectStackTrace = ExceptionKit.getProjectStackTraces(e);
        log.warn("服务异常：{}\nContext：{}，\n@See {}", e.getMsg(), JsonKit.toJson(ThreadContext.getValues()), projectStackTrace);
        return R.fail(e.getErrCode(), e.getMsg());
    }

    @ExceptionHandler({ServiceErrorException.class})
    public R<String> serviceErrorException(HttpServletRequest request, ServiceErrorException e) {
        String projectStackTrace = ExceptionKit.getProjectStackTraces(e);
        log.error("服务异常：{}\nContext：{}，\n@See {}", e.getMsg(), JsonKit.toJson(ThreadContext.getValues()), projectStackTrace);
        return R.fail(e.getErrCode(), e.getMsg());
    }

    @ExceptionHandler({RuntimeException.class})
    public R<String> runTimeException(HttpServletRequest request, RuntimeException e) {
        String projectStackTrace = ExceptionKit.getProjectStackTraces(e);
        ThreadContext.set(ContextConstants.SEE, projectStackTrace);
        log.error("运行时异常：{}，\n@See {}", e.getMessage(), projectStackTrace,e);
        return R.fail(ResultCodeEnum.FAIL.getCode(), e.getMessage());
    }


    @ExceptionHandler(HttpMessageNotReadableException.class)
    public R<String> httpMessageNotReadableException(HttpMessageNotReadableException e, HttpServletRequest request) {
        String projectStackTrace = ExceptionKit.getProjectStackTraces(e);
        ThreadContext.set(ContextConstants.SEE, projectStackTrace);
        String message = e.getMessage();
        String msg = ResultCodeEnum.PARAMETER_VALIDATION_FAILED.getDesc();
        if (CharSequenceUtil.containsAny(message, "JSON parse error: Unrecognized field")) {
            msg = String.format("无法正确的解析json类型的参数：%s", CharSequenceUtil.subBetween(message, "Could not read document:", " at "));
        }
        log.error("参数类型不匹配:{}，\n@See {}", msg, projectStackTrace);
        return R.fail(ResultCodeEnum.PARAMETER_VALIDATION_FAILED.getCode(), msg);
    }

    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public R<String> methodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request) {
        String projectStackTrace = ExceptionKit.getProjectStackTraces(e);
        ThreadContext.set(ContextConstants.SEE, projectStackTrace);
        String msg = "参数：[" + e.getName() +
                "]的传入值：[" + e.getValue() +
                "]与预期的字段类型：[" + Objects.requireNonNull(e.getRequiredType()).getName() + "]不匹配";
        log.error("异常信息为:{}，\n@See {}", msg, projectStackTrace);
        return R.fail(ResultCodeEnum.PARAMETER_VALIDATION_FAILED.getCode(), msg);
    }

    @ExceptionHandler(IllegalStateException.class)
    public R<String> illegalStateException(IllegalStateException e, HttpServletRequest request) {
        String projectStackTrace = ExceptionKit.getProjectStackTraces(e);
        ThreadContext.set(ContextConstants.SEE, projectStackTrace);
        log.error("IllegalStateException异常信息为:{}，\n@See {}", e.getMessage(), projectStackTrace);
        return R.fail(ResultCodeEnum.ILLEGAL_ARGUMENT_EX.getCode(), e.getMessage());
    }

    @ExceptionHandler(MissingServletRequestParameterException.class)
    public R<String> missingServletRequestParameterException(MissingServletRequestParameterException e, HttpServletRequest request) {
        String projectStackTrace = ExceptionKit.getProjectStackTraces(e);
        ThreadContext.set(ContextConstants.SEE, projectStackTrace);
        String msg = "缺少必须的[" + e.getParameterType() + "]类型的参数[" + e.getParameterName() + "]";
        log.error("MissingServletRequestParameterException异常信息为:{}，\n@See {}", msg, projectStackTrace);
        return R.fail(ResultCodeEnum.ILLEGAL_ARGUMENT_EX.getCode(), msg);
    }

    @ExceptionHandler(IllegalArgumentException.class)
    public R<String> illegalArgumentException(IllegalArgumentException e, HttpServletRequest request) {
        String projectStackTrace = ExceptionKit.getProjectStackTraces(e);
        ThreadContext.set(ContextConstants.SEE, projectStackTrace);
        log.error("IllegalArgumentException：{}，\n@See {}", e.getMessage(), projectStackTrace);
        return R.fail(ResultCodeEnum.ILLEGAL_ARGUMENT_EX.getCode(), ResultCodeEnum.ILLEGAL_ARGUMENT_EX.getDesc());
    }

    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public R<String> httpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException e, HttpServletRequest request) {
        String projectStackTrace = ExceptionKit.getProjectStackTraces(e);
        ThreadContext.set(ContextConstants.SEE, projectStackTrace);
        MediaType contentType = e.getContentType();
        String msg = "无效的Content-Type类型";
        if (contentType != null) {
            msg = "请求类型(Content-Type)[" + contentType + "] 与实际接口的请求类型不匹配";
        }
        log.error("报错信息为：{}，\n@See {}", msg, projectStackTrace);
        return R.fail(ResultCodeEnum.MEDIA_TYPE_EX.getCode(), msg);
    }

    @ExceptionHandler(MissingServletRequestPartException.class)
    public R<String> missingServletRequestPartException(MissingServletRequestPartException e, HttpServletRequest request) {
        String projectStackTrace = ExceptionKit.getProjectStackTraces(e);
        ThreadContext.set(ContextConstants.SEE, projectStackTrace);
        log.error("请求中必须至少包含一个有效文件,具体的错误信息为：{},\n@See {}", e.getMessage(), projectStackTrace);
        return R.fail(ResultCodeEnum.REQUIRED_FILE_PARAM_EX.getCode(), ResultCodeEnum.REQUIRED_FILE_PARAM_EX.getDesc());
    }

    @ExceptionHandler(ServletException.class)
    public R<String> servletException(ServletException e, HttpServletRequest request) {
        String projectStackTrace = ExceptionKit.getProjectStackTraces(e);
        ThreadContext.set(ContextConstants.SEE, projectStackTrace);
        String msg = "UT010016: Not a multi part request";
        if (msg.equalsIgnoreCase(e.getMessage())) {
            msg = ResultCodeEnum.REQUIRED_FILE_PARAM_EX.getDesc();
        }
        log.error("ServletException: {}，\n@See {}", e.getMessage(), projectStackTrace);
        return R.fail(ResultCodeEnum.REQUIRED_FILE_PARAM_EX.getCode(), msg);
    }

    @ExceptionHandler(MultipartException.class)
    public R<String> multipartException(MultipartException e, HttpServletRequest request) {
        String projectStackTrace = ExceptionKit.getProjectStackTraces(e);
        ThreadContext.set(ContextConstants.SEE, projectStackTrace);

        String msg = ResultCodeEnum.UPLOAD_FILE_ERROR.getDesc();
        if (StringUtils.isNotBlank(e.getMessage())) {
            msg = e.getMessage();
        }
        log.error("文件上传异常:{}，@See {}", msg, projectStackTrace);
        return R.fail(ResultCodeEnum.UPLOAD_FILE_ERROR.getCode(), msg);
    }

    /**
     * jsr 规范中的验证异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public R<String> constraintViolationException(ConstraintViolationException e, HttpServletRequest request) {
        String projectStackTrace = ExceptionKit.getProjectStackTraces(e);
        ThreadContext.set(ContextConstants.SEE, projectStackTrace);
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        String message = violations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(";"));

        if (StringUtils.isBlank(message)) {
            message = "校验异常";
        }
        log.error("jsr 规范中的验证异常:{}，@See {}", message, projectStackTrace);
        return R.fail(ResultCodeEnum.BASE_VALID_PARAM.getCode(), message);
    }


    /**
     * 返回状态码:405
     */
    @ExceptionHandler({HttpRequestMethodNotSupportedException.class})
    public R<String> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
        String projectStackTrace = ExceptionKit.getProjectStackTraces(e);
        ThreadContext.set(ContextConstants.SEE, projectStackTrace);
        String message = "返回状态405，错误的url请求";
        if (StringUtils.isNotBlank(e.getMessage())) {
            message = e.getMessage();
        }
        log.error("HttpRequestMethodNotSupportedException:{}，@See {}", message, projectStackTrace);
        return R.fail(ResultCodeEnum.METHOD_NOT_ALLOWED.getCode(), message);
    }


    @ExceptionHandler(SQLException.class)
    public R<String> sqlException(SQLException e, HttpServletRequest request) {
        String projectStackTrace = ExceptionKit.getProjectStackTraces(e);
        ThreadContext.set(ContextConstants.SEE, projectStackTrace);
        String message = e.getMessage();
        if (StringUtils.isNotBlank(e.getMessage())) {
            message = e.getCause().getMessage();
        }
        log.error("SQLException:{}，@See {}", message, projectStackTrace);
        return R.fail(ResultCodeEnum.SQL_EX.getCode(), message);
    }
}
